Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support voice agent api #360

Merged
merged 5 commits into from
Feb 3, 2025
Merged

feat: support voice agent api #360

merged 5 commits into from
Feb 3, 2025

Conversation

naomi-lgbt
Copy link
Contributor

@naomi-lgbt naomi-lgbt commented Jan 28, 2025

Proposed changes

Types of changes

What types of changes does your code introduce to the community .NET SDK?
Put an x in the boxes that apply

  • Bugfix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update or tests (if none of the other choices apply)

Checklist

Put an x in the boxes that apply. You can also fill these out after creating the PR. If you're unsure about any of them, don't hesitate to ask. We're here to help! This is simply a reminder of what we are going to look for before merging your code.

  • I have read the CONTRIBUTING doc
  • I have lint'ed all of my code using repo standards
  • I have added tests that prove my fix is effective or that my feature works
  • I have added necessary documentation (if appropriate)

Further comments

Summary by CodeRabbit

  • New Features

    • Added WebSocket Agent Client for Deepgram API.
    • Introduced new WebSocket communication methods for audio processing.
    • Implemented agent-specific event handling and configuration options.
  • New Projects

    • Added new solution projects for agent WebSocket functionality.
    • Created example project demonstrating agent WebSocket client usage.
  • Enhancements

    • Expanded client factory with new agent WebSocket client creation methods.
    • Added comprehensive model classes for agent interactions.
    • Implemented detailed response handling for various agent events.

Copy link
Contributor

coderabbitai bot commented Jan 28, 2025

Warning

Rate limit exceeded

@naomi-lgbt has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 9 minutes and 8 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 4242619 and b05b766.

📒 Files selected for processing (2)
  • Deepgram/Clients/Interfaces/v2/IAgentWebSocketClient.cs (1 hunks)
  • README.md (1 hunks)

Walkthrough

This pull request introduces several new projects to the Deepgram .NET SDK, enhancing WebSocket-based agent communication. It adds four projects to the solution, implements a new WebSocket client class for agent interactions, and includes methods for creating agent WebSocket clients. Additionally, multiple model classes for agent-related responses and configurations are introduced, along with a sample agent application that demonstrates the new functionalities.

Changes

File/Directory Change Summary
Deepgram.Dev.sln Added 4 new projects: "agent", "websocket", "simple", and "Agent"
Deepgram/AgentWebSocketClient.cs New WebSocket client class for agent communication
Deepgram/ClientFactory.cs Added methods for creating agent WebSocket clients
Deepgram/Clients/Agent/v2/Websocket/ New namespace with comprehensive WebSocket client implementation
Deepgram/Models/Agent/v2/WebSocket/ Added multiple model classes for agent-related responses and configurations
examples/agent/websocket/simple/ New example project demonstrating WebSocket agent communication
.gitignore Added entry to ignore log.txt

Sequence Diagram

sequenceDiagram
    participant Client as WebSocket Client
    participant Deepgram as Deepgram API
    
    Client->>Deepgram: Connect with Configuration
    Deepgram-->>Client: Welcome Response
    Client->>Deepgram: Start Audio Streaming
    Deepgram-->>Client: Audio Responses
    Client->>Deepgram: Send Text/Function Calls
    Deepgram-->>Client: Conversation Text/Function Responses
    Client->>Deepgram: Close Connection
Loading

Possibly related PRs

  • Reintroduce TTS WS #316: This PR adds a new project named "websocket" to the solution, which is directly related to the main PR's addition of a "websocket" project, indicating a focus on WebSocket functionality.
  • Update WebSocket Listen Reconnect Example #338: This PR updates the WebSocket Listen example, which aligns with the main PR's focus on enhancing the solution's structure for WebSocket communication.
  • Throw Exception on Failed WS Connect #340: This PR introduces error handling for failed WebSocket connections, which is relevant to the main PR's context of adding new WebSocket projects and functionalities.

Suggested reviewers

  • jpvajda
  • lukeocodes
  • SandraRodgers

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 22

🧹 Nitpick comments (73)
Deepgram/Models/Agent/v2/WebSocket/UserStartedSpeakingResponse.cs (1)

17-23: Optimize ToString implementation and add missing using directives.

The current implementation has several potential issues:

  1. Missing using directives for System.Text.Json and System.Text.RegularExpressions
  2. No null check before serialization
  3. Unnecessary use of Regex.Unescape on every call
  4. Could benefit from caching since the object is immutable

Add these using directives at the top of the file:

+using System.Text.Json;
+using System.Text.RegularExpressions;

Consider optimizing the implementation:

+    private string? _cachedString;
+
     /// <summary>
     /// Override ToString method to serialize the object
     /// </summary>
     public override string ToString()
     {
-        return Regex.Unescape(JsonSerializer.Serialize(this, JsonSerializeOptions.DefaultOptions));
+        return _cachedString ??= JsonSerializer.Serialize(this, JsonSerializeOptions.DefaultOptions);
     }

Note: Removed Regex.Unescape as it's typically not needed for JSON serialization unless you're dealing with special characters that need unescaping.

Deepgram/Models/Agent/v2/WebSocket/AgentType.cs (1)

9-28: Add XML documentation and consider enum value organization.

The enum implementation could be improved in several ways:

  1. Add XML documentation for the enum and its values to support IntelliSense.
  2. Explicitly set values for agent-specific states to prevent issues if WebSocketType values change.
  3. Consider grouping related states using regions or comments.

Here's a suggested implementation:

+/// <summary>
+/// Represents the various states and message types in the agent WebSocket communication.
+/// </summary>
 public enum AgentType
 {
     // Base WebSocket states
     Open = WebSocketType.Open,
     Close = WebSocketType.Close,
     Unhandled = WebSocketType.Unhandled,
     Error = WebSocketType.Error,
 
     // Agent conversation states
-    Welcome,
-    ConversationText,
-    UserStartedSpeaking,
-    AgentThinking,
+    Welcome = 100,
+    ConversationText = 101,
+    UserStartedSpeaking = 102,
+    AgentThinking = 103,
 
     // Function calling states
-    FunctionCallRequest,
-    FunctionCalling,
+    FunctionCallRequest = 200,
+    FunctionCalling = 201,
 
     // Audio states
-    AgentStartedSpeaking,
-    AgentAudioDone,
-    Audio,
+    AgentStartedSpeaking = 300,
+    AgentAudioDone = 301,
+    Audio = 302,
 
     // Configuration states
-    InjectionRefused,
-    SettingsApplied,
-    InstructionsUpdated,
-    SpeakUpdated,
+    InjectionRefused = 400,
+    SettingsApplied = 401,
+    InstructionsUpdated = 402,
+    SpeakUpdated = 403,
 }
Deepgram/Models/Agent/v2/WebSocket/Audio.cs (1)

7-16: Consider making properties immutable for thread safety.

The record type suggests immutability, but the properties have public setters. For thread safety and to better align with record semantics, consider making the properties init-only.

-    public Input Input { get; set; } = new Input();
+    public Input Input { get; init; } = new Input();

-    public Output Output { get; set; } = new Output();
+    public Output Output { get; init; } = new Output();
Deepgram/Models/Agent/v2/WebSocket/Output.cs (2)

9-15: Document the significance of default values.

The default values ("linear16" for Encoding and 24000 for SampleRate) seem to be magic numbers. Consider:

  1. Adding XML documentation to explain their significance
  2. Using constants to improve maintainability
+    /// <summary>
+    /// Default audio encoding format compatible with Deepgram's voice agent API.
+    /// </summary>
+    private const string DefaultEncoding = "linear16";
+
+    /// <summary>
+    /// Default sample rate (in Hz) for optimal voice processing.
+    /// </summary>
+    private const long DefaultSampleRate = 24000;

-    public string Encoding { get; set; } = "linear16";
+    public string Encoding { get; set; } = DefaultEncoding;

-    public long? SampleRate { get; set; } = 24000;
+    public long? SampleRate { get; set; } = DefaultSampleRate;

17-23: Add validation for Bitrate property.

Consider adding validation to ensure the Bitrate value is positive when set.

+    private long? _bitrate;

     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
     [JsonPropertyName("bitrate")]
-    public long? Bitrate { get; set; }
+    public long? Bitrate
+    {
+        get => _bitrate;
+        set
+        {
+            if (value.HasValue && value.Value <= 0)
+                throw new ArgumentOutOfRangeException(nameof(value), "Bitrate must be positive");
+            _bitrate = value;
+        }
+    }
Deepgram/Clients/Agent/v2/Websocket/Client.cs (8)

18-25: Add XML summary for the constructor.
The constructor lacks an XML doc comment explaining its purpose, parameters, and usage. Providing it helps maintain consistent documentation and improves clarity for consumers of this class.

 /// <param name="apiKey">Required DeepgramApiKey</param>
 /// <param name="deepgramClientOptions"><see cref="IDeepgramClientOptions"/> for HttpClient Configuration</param>
+/// <summary>
+/// Initializes a new instance of the <see cref="Client"/> class, setting up
+/// the Deepgram API key and client options for Agent WebSocket communication.
+/// </summary>
 public Client(string? apiKey = null, IDeepgramClientOptions? options = null) : base(apiKey, options)
 {
     Log.Verbose("AgentWSClient", "ENTER");

27-44: Align event naming or add individual summaries.
The private events from _audioReceived to _speakUpdatedReceived share a single documentation snippet indicating “Fires when an event is received,” but each event actually targets a different response type. To make the code more self-explanatory, add or adjust comments to clarify when each event is fired and its intended usage.


105-129: Consolidate wrapped event subscriptions for typed “Open” / “Close” / “Error” / “Unhandled” events.
Each of these methods follows a similar pattern: create a wrapped handler, cast arguments, and subscribe via the base method. Consider creating a helper method to reduce duplication and improve readability.

-private async Task<bool> SubscribeForCommonEvent<TCommon, TSpecific>(
-    EventHandler<TSpecific> userHandler,
-    Func<Common.TCommon, TSpecific> castFunc)
-{
-    // code to wrap and subscribe
-}

Also applies to: 389-410


135-387: Consider adding unsubscribe support & a unified pattern for these subscription methods.
All subscription methods add their handlers to private events but never provide an unsubscribe mechanism. If a consumer repeatedly subscribes to the same event type, memory leaks or unexpected behavior may occur. Additionally, these subscription methods share repeated code blocks. Refactoring them into a common “subscribe event” helper (or using a dictionary-based approach) would reduce duplication and potential confusion.


483-523: Key concurrency detail in keep-alive loop.
The loop sleeps for a hard-coded 5 seconds and continues indefinitely until disconnected or canceled. Consider making the interval configurable. Also ensure that _cancellationTokenSource is disposed once completed.


554-555: Typo in log message “Excepton”.
Minor spelling error in the log message could cause confusion during troubleshooting or searching logs.

-Log.Verbose("ProcessDataReceived", $"Excepton: {ex}");
+Log.Verbose("ProcessDataReceived", $"Exception: {ex}");

560-809: Handle invalid or missing “type” gracefully in the switch statement.
When data.RootElement.GetProperty("type") is missing or invalid, Enum.Parse will fail with an exception. To improve robustness, consider using Enum.TryParse and handle unrecognized event types more gracefully.

-var val = Enum.Parse(typeof(AgentType), data.RootElement.GetProperty("type").GetString()!);
+if (!Enum.TryParse<AgentType>(data.RootElement.GetProperty("type").GetString(), out var val))
+{
+    Log.Warning("ProcessTextMessage", "Received unknown or invalid AgentType.");
+    base.ProcessTextMessage(result, ms);
+    return;
+}

835-858: Prevent potential double slash in URI construction.
When appending the UriSegments.AGENT constant to baseAddress, ensure no extra slash is added if baseAddress already ends with one. This avoids issues with misconfigured endpoints or future changes to UriSegments.AGENT.

-if (baseAddress.EndsWith("/"))
-    return new Uri($"{baseAddress}{UriSegments.AGENT}");
-else
-    return new Uri($"{baseAddress}/{UriSegments.AGENT}");
+if (!baseAddress.EndsWith("/"))
+{
+    baseAddress += "/";
+}
+return new Uri($"{baseAddress}{UriSegments.AGENT}");
Deepgram/Clients/Agent/v2/Websocket/ResponseEvent.cs (1)

7-10: Clarify usage of primary constructor vs. record syntax.
C# primary constructors are typically used in records. Given this class inherits EventArgs, consider using a standard class or record instead. If immutability is desired, a record with read-only properties might be preferable. Additionally, a brief summary comment clarifying usage would help.

-public class ResponseEvent<T>(T? response) : EventArgs
+/// <summary>
+/// Encapsulates an event response of type T.
+/// </summary>
+public class ResponseEvent<T> : EventArgs
+{
+    public ResponseEvent(T? response)
+    {
+        Response = response;
+    }
+
     public T? Response { get; set; }
 }
Deepgram/AgentWebSocketClient.cs (1)

13-18: Add summary doc & rename constructor parameters to match base usage.
The constructor lacks an XML doc comment. Also, for clarity, consider naming the parameter options instead of deepgramClientOptions, mirroring the base constructor’s signature. This can reduce confusion for SDK users.

 /// <summary>
 /// Implements the latest supported version of the Agent Client.
 /// </summary>
 public class AgentWebSocketClient : Client
 {
-    public AgentWebSocketClient(string apiKey = "", DeepgramWsClientOptions? deepgramClientOptions = null)
-        : base(apiKey, deepgramClientOptions)
+    /// <summary>
+    /// Initializes a new instance of the <see cref="AgentWebSocketClient"/> class for contemporary Deepgram agent usage.
+    /// </summary>
+    public AgentWebSocketClient(string apiKey = "", DeepgramWsClientOptions? options = null)
+        : base(apiKey, options)
     {
     }
 }
Deepgram/Clients/Agent/v2/Websocket/UriSegments.cs (2)

9-11: Consider making the agent URI configurable.

While hardcoding "agent.deepgram.com" works for production, it might limit testing against different environments. Consider making this configurable through environment variables or configuration settings.


7-16: Add XML documentation for better IntelliSense support.

Consider adding XML documentation to describe the purpose and usage of these constants.

 public static class UriSegments
 {
+    /// <summary>
+    /// The base URI for Deepgram's agent API endpoint.
+    /// </summary>
     public const string AGENT_URI = "agent.deepgram.com";

+    /// <summary>
+    /// The agent endpoint segment used in API routes.
+    /// </summary>
     public const string AGENT = "agent";
 }
Deepgram/Models/Agent/v2/WebSocket/Provider.cs (2)

9-11: Add XML documentation for the Type property.

Add XML documentation to describe the expected values and purpose of the Type property.

+    /// <summary>
+    /// Gets or sets the provider type.
+    /// </summary>
     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
     [JsonPropertyName("type")]
     public string Type { get; set; }

7-12: Consider using an enum for the Type property.

Using a string for Type could lead to runtime errors if invalid values are provided. Consider using an enum to enforce valid provider types at compile-time.

+public enum ProviderType
+{
+    [JsonPropertyName("type1")]
+    Type1,
+    [JsonPropertyName("type2")]
+    Type2,
+    // Add other valid types
+}

 public record Provider
 {
     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
     [JsonPropertyName("type")]
-    public string Type { get; set; }
+    public ProviderType Type { get; set; }
Deepgram/Models/Agent/v2/WebSocket/Properties.cs (2)

13-13: Remove extra blank line.

Remove the unnecessary blank line to maintain consistent formatting.


9-11: Add XML documentation for the Item property.

Add XML documentation to describe the purpose and nullable nature of the Item property.

+    /// <summary>
+    /// Gets or sets the item associated with these properties.
+    /// </summary>
     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
     [JsonPropertyName("item")]
     public Item? Item { get; set; }
Deepgram/Models/Agent/v2/WebSocket/Listen.cs (2)

11-11: Consider making the model version configurable.

The default model version "nova-2" is hardcoded. Consider making this configurable to support future model versions without code changes.


9-11: Add XML documentation and validation for the Model property.

Add XML documentation and consider adding validation to ensure the model version is supported.

+    /// <summary>
+    /// Gets or sets the model version to use for speech recognition.
+    /// Default value is "nova-2".
+    /// </summary>
     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
     [JsonPropertyName("model")]
-    public string Model { get; set; } = "nova-2";
+    public string Model
+    {
+        get => _model;
+        set
+        {
+            if (string.IsNullOrEmpty(value))
+                throw new ArgumentException("Model version cannot be null or empty");
+            _model = value;
+        }
+    }
+
+    private string _model = "nova-2";
Deepgram/Models/Agent/v2/WebSocket/Speak.cs (2)

7-12: Add XML documentation and consider making the model name configurable.

The Model property lacks XML documentation. Additionally, the hardcoded model name "aura-asteria-en" should ideally be configurable or defined as a constant.

 public record Speak
 {
+    /// <summary>
+    /// The voice model to use for speech synthesis.
+    /// </summary>
+    /// <remarks>
+    /// Available models:
+    /// - aura-asteria-en: Default English voice model
+    /// </remarks>
     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
     [JsonPropertyName("model")]
-    public string Model { get; set; } = "aura-asteria-en";
+    public string Model { get; set; } = SpeakModels.Default;
 }

+ public static class SpeakModels
+ {
+     public const string Default = "aura-asteria-en";
+ }

16-19: Add return value documentation for ToString.

The XML documentation for the ToString override should include information about the return value.

     /// <summary>
     /// Override ToString method to serialize the object
     /// </summary>
+    /// <returns>A JSON string representation of the object.</returns>
Deepgram/Models/Agent/v2/WebSocket/KeepAliveSchema.cs (1)

7-15: Consider using a record and fix documentation.

  1. The XML documentation incorrectly mentions "SettingsConfiguration".
  2. Since Type is effectively readonly, consider:
    • Making this a record
    • Using init-only property
- public class KeepAliveSchema
+ public record KeepAliveSchema
 {
     /// <summary>
-    /// SettingsConfiguration event type.
+    /// The keep-alive message type.
     /// </summary>
     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
     [JsonPropertyName("type")]
-    public string? Type { get; } = AgentClientTypes.KeepAlive;
+    public string? Type { get; init; } = AgentClientTypes.KeepAlive;
Deepgram/Models/Agent/v2/WebSocket/Header.cs (1)

17-23: Add return value documentation for ToString.

The XML documentation for the ToString override should include information about the return value.

     /// <summary>
     /// Override ToString method to serialize the object
     /// </summary>
+    /// <returns>A JSON string representation of the object.</returns>
Deepgram/Models/Agent/v2/WebSocket/Item.cs (1)

9-15: Add XML documentation for properties.

Properties lack XML documentation. Add summary tags to describe their purpose and usage.

 public record Item
 {
     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
     [JsonPropertyName("type")]
+    /// <summary>
+    /// Gets or sets the type of the item.
+    /// </summary>
     public string? Type { get; set; }

     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
     [JsonPropertyName("description")]
+    /// <summary>
+    /// Gets or sets the description of the item.
+    /// </summary>
     public string? Description { get; set; }
Deepgram/Models/Agent/v2/WebSocket/Context.cs (1)

13-15: Add XML documentation for properties.

Properties lack XML documentation. Add summary tags to describe their purpose and usage.

+    /// <summary>
+    /// Gets or sets the list of messages in the content.
+    /// </summary>
     public List<object>? Messages { get; set; }

+    /// <summary>
+    /// Gets or sets a value indicating whether replay is enabled.
+    /// </summary>
     public bool? Replay { get; set; }
Deepgram/Models/Agent/v2/WebSocket/SettingsAppliedResponse.cs (3)

1-1: Update copyright year to 2024.

The copyright year should be 2024 as we're currently in January 2025.

-// Copyright 2025 Deepgram .NET SDK contributors. All Rights Reserved.
+// Copyright 2024 Deepgram .NET SDK contributors. All Rights Reserved.

9-11: Fix incorrect XML documentation.

The XML documentation refers to "SettingsConfiguration" instead of "SettingsApplied".

    /// <summary>
-   /// SettingsConfiguration event type.
+   /// SettingsApplied event type.
    /// </summary>

17-20: Enhance XML documentation for ToString method.

Add return value documentation using <returns> tag.

    /// <summary>
    /// Override ToString method to serialize the object
    /// </summary>
+   /// <returns>A JSON string representation of the object.</returns>
Deepgram/Models/Agent/v2/WebSocket/Input.cs (3)

9-11: Add XML documentation for Encoding property.

Document the property's purpose and default value.

+   /// <summary>
+   /// Gets or sets the audio encoding format.
+   /// Default value is "linear16".
+   /// </summary>
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
    [JsonPropertyName("encoding")]
    public string Encoding { get; set; } = "linear16";

13-15: Add XML documentation for SampleRate property.

Document the property's purpose and default value.

+   /// <summary>
+   /// Gets or sets the audio sample rate in Hz.
+   /// Default value is 24000.
+   /// </summary>
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
    [JsonPropertyName("sample_rate")]
    public long? SampleRate { get; set; } = 24000;

7-16: Add input validation for Encoding and SampleRate properties.

Consider adding validation to ensure valid values are set:

  1. Encoding should be validated against supported formats
  2. SampleRate should be within acceptable range
 public record Input
 {
+    private string _encoding = "linear16";
+    private long? _sampleRate = 24000;
+
     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
     [JsonPropertyName("encoding")]
-    public string Encoding { get; set; } = "linear16";
+    public string Encoding
+    {
+        get => _encoding;
+        set
+        {
+            if (!IsValidEncoding(value))
+                throw new ArgumentException("Invalid encoding format", nameof(value));
+            _encoding = value;
+        }
+    }

     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
     [JsonPropertyName("sample_rate")]
-    public long? SampleRate { get; set; } = 24000;
+    public long? SampleRate
+    {
+        get => _sampleRate;
+        set
+        {
+            if (value.HasValue && (value < 8000 || value > 48000))
+                throw new ArgumentException("Sample rate must be between 8000 and 48000 Hz", nameof(value));
+            _sampleRate = value;
+        }
+    }
+
+    private static bool IsValidEncoding(string encoding) =>
+        encoding?.ToLowerInvariant() switch
+        {
+            "linear16" or "mulaw" or "alaw" => true,
+            _ => false
+        };
Deepgram/Models/Agent/v2/WebSocket/InjectionRefusedResponse.cs (2)

1-1: Update copyright year to 2024.

The copyright year should be 2024 as we're currently in January 2025.

-// Copyright 2025 Deepgram .NET SDK contributors. All Rights Reserved.
+// Copyright 2024 Deepgram .NET SDK contributors. All Rights Reserved.

9-11: Fix incorrect XML documentation.

The XML documentation refers to "SettingsConfiguration" instead of "InjectionRefused".

    /// <summary>
-   /// SettingsConfiguration event type.
+   /// InjectionRefused event type.
    /// </summary>
Deepgram/Models/Agent/v2/WebSocket/FunctionCallRequestResponse.cs (2)

9-11: Fix incorrect XML documentation.

The XML documentation refers to "SettingsConfiguration" instead of "FunctionCallRequest".

    /// <summary>
-   /// SettingsConfiguration event type.
+   /// FunctionCallRequest event type.
    /// </summary>

1-26: Ensure consistency across WebSocket response models.

Consider the following architectural improvements:

  1. Standardize property mutability:
    • Input.cs uses mutable properties
    • Other models use init-only properties
  2. Create a base response class/interface:
    • All models have Type and ToString() implementation
    • Common serialization logic could be shared
  3. Add XML documentation templates:
    • Standardize documentation format
    • Include examples in documentation

Example base class:

public abstract record AgentResponseBase
{
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
    [JsonPropertyName("type")]
    [JsonConverter(typeof(JsonStringEnumConverter))]
    public AgentType? Type { get; init; }

    public override string ToString() =>
        Regex.Unescape(JsonSerializer.Serialize(this, JsonSerializeOptions.DefaultOptions));
}
Deepgram/Models/Agent/v2/WebSocket/UpdateSpeakSchema.cs (3)

9-14: Fix incorrect XML documentation for Type property

The XML comment incorrectly states "SettingsConfiguration event type" when it should describe the UpdateSpeak event type.

     /// <summary>
-    /// SettingsConfiguration event type.
+    /// UpdateSpeak event type.
     /// </summary>

16-18: Add XML documentation for Model property

Missing XML documentation for the Model property. Add a summary describing its purpose and any constraints.

+    /// <summary>
+    /// The model to be used for speech synthesis.
+    /// </summary>
     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
     [JsonPropertyName("model")]

23-26: Add error handling to ToString method

The ToString method should handle potential serialization errors to prevent runtime exceptions.

     public override string ToString()
     {
-        return Regex.Unescape(JsonSerializer.Serialize(this, JsonSerializeOptions.DefaultOptions));
+        try
+        {
+            return Regex.Unescape(JsonSerializer.Serialize(this, JsonSerializeOptions.DefaultOptions));
+        }
+        catch (JsonException ex)
+        {
+            return $"{{\"error\": \"Failed to serialize {nameof(UpdateSpeakSchema)}: {ex.Message}\"}}";
+        }
     }
Deepgram/Models/Agent/v2/WebSocket/InjectAgentMessageSchema.cs (1)

16-18: Add XML documentation for Message property

Missing XML documentation for the Message property.

+    /// <summary>
+    /// The message to be injected into the agent's processing workflow.
+    /// </summary>
     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
     [JsonPropertyName("message")]
Deepgram/Models/Agent/v2/WebSocket/UpdateInstructionsSchema.cs (1)

16-18: Add XML documentation for Instructions property

Missing XML documentation for the Instructions property.

+    /// <summary>
+    /// The updated instructions for the agent's behavior.
+    /// </summary>
     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
     [JsonPropertyName("instructions")]
Deepgram/Models/Agent/v2/WebSocket/WelcomeResponse.cs (2)

17-19: Add XML documentation for SessionId property

Missing XML documentation for the SessionId property.

+    /// <summary>
+    /// The unique identifier for the WebSocket session.
+    /// </summary>
     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
     [JsonPropertyName("session_id")]

1-28: Consider introducing a base class for common functionality

All schema/response classes share common patterns for JSON serialization and ToString implementation. Consider extracting these into a base class or interface to promote DRY principles and ensure consistent error handling.

Example base class:

public abstract class WebSocketMessageBase
{
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
    [JsonPropertyName("type")]
    public abstract string? Type { get; }

    public override string ToString()
    {
        try
        {
            return Regex.Unescape(JsonSerializer.Serialize(this, JsonSerializeOptions.DefaultOptions));
        }
        catch (JsonException ex)
        {
            return $"{{\"error\": \"Failed to serialize {GetType().Name}: {ex.Message}\"}}";
        }
    }
}
Deepgram/Models/Agent/v2/WebSocket/AgentThinkingResponse.cs (2)

9-11: Fix incorrect XML documentation.

The XML comment for the Type property incorrectly mentions "SettingsConfiguration" instead of "AgentThinking".

-    /// SettingsConfiguration event type.
+    /// The type of the agent event, which is AgentThinking.

17-19: Add XML documentation for the Content property.

Missing XML documentation for the Content property.

+    /// <summary>
+    /// The content of the agent's thinking response.
+    /// </summary>
     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
Deepgram/Models/Agent/v2/WebSocket/Agent.cs (3)

9-11: Add XML documentation for the Listen property.

Missing XML documentation for the Listen property.

+    /// <summary>
+    /// Configuration for the agent's listening capabilities.
+    /// </summary>
     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]

13-15: Add XML documentation for the Think property.

Missing XML documentation for the Think property.

+    /// <summary>
+    /// Configuration for the agent's thinking capabilities.
+    /// </summary>
     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]

17-19: Add XML documentation for the Speak property.

Missing XML documentation for the Speak property.

+    /// <summary>
+    /// Configuration for the agent's speaking capabilities.
+    /// </summary>
     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
Deepgram/Models/Agent/v2/WebSocket/FunctionCallingResponse.cs (2)

9-11: Fix incorrect XML documentation.

The XML comment for the Type property incorrectly mentions "SettingsConfiguration" instead of "FunctionCalling".

-    /// SettingsConfiguration event type.
+    /// The type of the agent event, which is FunctionCalling.

17-19: Add XML documentation for the SessionId property.

Missing XML documentation for the SessionId property.

+    /// <summary>
+    /// The unique identifier for the current session.
+    /// </summary>
     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
Deepgram/Models/Agent/v2/WebSocket/Parameters.cs (3)

13-15: Add XML documentation for the Properties property.

Missing XML documentation for the Properties property.

+    /// <summary>
+    /// The properties configuration for the parameters.
+    /// </summary>
     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]

17-19: Add XML documentation for the ParametersRequired property.

Missing XML documentation for the ParametersRequired property.

+    /// <summary>
+    /// The list of required parameter names.
+    /// </summary>
     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]

21-22: Remove extra blank lines.

There are two consecutive blank lines between properties and the ToString method.

     public List<string>? ParametersRequired { get; set; }

-
-
     /// <summary>
Deepgram/Models/Agent/v2/WebSocket/AudioResponse.cs (2)

7-30: Add XML documentation for the Dispose method.

The class has good documentation overall, but the Dispose method is missing XML documentation. Consider adding it to maintain consistency.

+    /// <summary>
+    /// Disposes of the underlying stream if it exists.
+    /// </summary>
     public void Dispose()
     {
         Stream?.Dispose();
         Stream = null;
     }

22-23: Consider formalizing the note about ToString().

The note about ToString() could be more formal using XML documentation.

-    // NOTE: There isn't a ToString() function because this will cause an odd Exception to be thrown:
-    // InvalidOperationException: "Timeouts are not supported on this stream."
+    /// <summary>
+    /// ToString() is intentionally not overridden as it would cause an InvalidOperationException
+    /// with the message "Timeouts are not supported on this stream."
+    /// </summary>
Deepgram/Models/Agent/v2/WebSocket/FunctionCallResponseSchema.cs (3)

9-11: Fix incorrect XML documentation.

The documentation for the Type property incorrectly states "SettingsConfiguration event type" when it should describe the function call response type.

     /// <summary>
-    /// SettingsConfiguration event type.
+    /// The type of the function call response.
     /// </summary>

16-22: Add XML documentation for missing properties.

The FunctionCallId and Output properties are missing XML documentation.

+    /// <summary>
+    /// The unique identifier of the function call.
+    /// </summary>
     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
     [JsonPropertyName("function_call_id")]
     public string? FunctionCallId { get; set; }

+    /// <summary>
+    /// The output returned by the function call.
+    /// </summary>
     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
     [JsonPropertyName("output")]
     public string? Output { get; set; }

7-31: Consider using a record for immutability.

Since this class represents a response schema, consider making it immutable by converting it to a record with init-only properties.

-public class FunctionCallResponseSchema
+public record FunctionCallResponseSchema
 {
     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
     [JsonPropertyName("type")]
-    public string? Type { get; } = AgentClientTypes.FunctionCallResponse;
+    public string? Type { get; init; } = AgentClientTypes.FunctionCallResponse;

     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
     [JsonPropertyName("function_call_id")]
-    public string? FunctionCallId { get; set; }
+    public string? FunctionCallId { get; init; }

     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
     [JsonPropertyName("output")]
-    public string? Output { get; set; }
+    public string? Output { get; init; }
Deepgram/Models/Agent/v2/WebSocket/ConversationTextResponse.cs (3)

9-11: Fix incorrect XML documentation.

The documentation for the Type property incorrectly states "SettingsConfiguration event type" when it should describe the conversation text type.

     /// <summary>
-    /// SettingsConfiguration event type.
+    /// The type of the conversation text response.
     /// </summary>

17-23: Add XML documentation for missing properties.

The Role and Content properties are missing XML documentation.

+    /// <summary>
+    /// The role of the conversation participant (e.g., user, assistant).
+    /// </summary>
     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
     [JsonPropertyName("role")]
     public string? Role { get; set; }

+    /// <summary>
+    /// The content of the conversation message.
+    /// </summary>
     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
     [JsonPropertyName("content")]
     public string? Content { get; set; }

7-32: Consider using init-only properties.

Since this is already a record, consider making the properties init-only for better immutability.

     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
     [JsonPropertyName("role")]
-    public string? Role { get; set; }
+    public string? Role { get; init; }

     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
     [JsonPropertyName("content")]
-    public string? Content { get; set; }
+    public string? Content { get; init; }
Deepgram/Models/Agent/v2/WebSocket/Think.cs (3)

9-23: Add XML documentation for all properties.

All properties are missing XML documentation.

+    /// <summary>
+    /// The provider configuration for the think operation.
+    /// </summary>
     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
     [JsonPropertyName("provider")]
     public Provider? Provider { get; set; } = new Provider();

+    /// <summary>
+    /// The model to be used for thinking.
+    /// </summary>
     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
     [JsonPropertyName("model")]
     public string? Model { get; set; }

+    /// <summary>
+    /// The instructions for the think operation.
+    /// </summary>
     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
     [JsonPropertyName("instructions")]
     public string? Instructions { get; set; }

+    /// <summary>
+    /// The list of functions available during the think operation.
+    /// </summary>
     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
     [JsonPropertyName("functions")]
     public List<Function>? Functions { get; set; }

25-26: Remove unnecessary blank lines.

There are two consecutive blank lines that should be removed.

     public List<Function>? Functions { get; set; }

-
-
     /// <summary>

7-34: Consider using init-only properties.

Since this is a record, consider making the properties init-only for better immutability.

     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
     [JsonPropertyName("provider")]
-    public Provider? Provider { get; set; } = new Provider();
+    public Provider? Provider { get; init; } = new Provider();

     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
     [JsonPropertyName("model")]
-    public string? Model { get; set; }
+    public string? Model { get; init; }

     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
     [JsonPropertyName("instructions")]
-    public string? Instructions { get; set; }
+    public string? Instructions { get; init; }

     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
     [JsonPropertyName("functions")]
-    public List<Function>? Functions { get; set; }
+    public List<Function>? Functions { get; init; }
Deepgram/Models/Agent/v2/WebSocket/SettingsConfigurationSchema.cs (2)

16-26: Add XML documentation for public properties.

The Audio, Agent, and Context properties are missing XML documentation. Adding documentation would improve API usability and maintain consistency with other documented members.


31-34: Consider optimizing ToString() implementation.

The current implementation uses Regex.Unescape which might be unnecessary overhead if the serialized JSON doesn't contain escaped characters. Consider using JsonSerializer.Serialize directly unless there's a specific need for unescaping.

-        return Regex.Unescape(JsonSerializer.Serialize(this, JsonSerializeOptions.DefaultOptions));
+        return JsonSerializer.Serialize(this, JsonSerializeOptions.DefaultOptions);
Deepgram/Models/Agent/v2/WebSocket/AgentStartedSpeakingResponse.cs (1)

9-11: Fix incorrect XML documentation.

The XML documentation for the Type property incorrectly states "SettingsConfiguration event type" when it should describe the agent started speaking event type.

-    /// SettingsConfiguration event type.
+    /// The type of the event, indicating that the agent has started speaking.
Deepgram/Clients/Interfaces/v2/IAgentWebSocketClient.cs (1)

121-133: Clarify the distinction between binary and text messages.

The documentation doesn't clearly explain the difference between SendBinary and SendMessage. Both accept byte arrays but their purposes seem different. Consider adding detailed documentation about when to use each method.

examples/agent/websocket/simple/Program.cs (2)

47-47: Extract magic number to constant.

The hardcoded value 7 should be extracted to a named constant for better maintainability.

+private const int AUDIO_FILE_TIMEOUT_SECONDS = 7;
-if (DateTime.Now.Subtract(lastAudioTime).TotalSeconds > 7)
+if (DateTime.Now.Subtract(lastAudioTime).TotalSeconds > AUDIO_FILE_TIMEOUT_SECONDS)

210-218: Enhance cleanup error handling.

While resources are properly disposed, the error handling could be more robust.

-// Stop the microphone
-microphone.Stop();
+try
+{
+    Console.WriteLine("Stopping microphone...");
+    microphone.Stop();
+    
+    Console.WriteLine("Stopping WebSocket connection...");
+    await agentClient.Stop();
+    
+    Console.WriteLine("Terminating libraries...");
+    Deepgram.Microphone.Library.Terminate();
+    Deepgram.Library.Terminate();
+    
+    Console.WriteLine("Cleanup completed successfully");
+}
+catch (Exception ex)
+{
+    Console.WriteLine($"Error during cleanup: {ex.Message}");
+    // Consider logging to file or telemetry service
+}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4c9955d and 23e95af.

📒 Files selected for processing (50)
  • Deepgram.Dev.sln (3 hunks)
  • Deepgram/AgentWebSocketClient.cs (1 hunks)
  • Deepgram/ClientFactory.cs (2 hunks)
  • Deepgram/Clients/Agent/v2/Websocket/Client.cs (1 hunks)
  • Deepgram/Clients/Agent/v2/Websocket/Constants.cs (1 hunks)
  • Deepgram/Clients/Agent/v2/Websocket/ResponseEvent.cs (1 hunks)
  • Deepgram/Clients/Agent/v2/Websocket/UriSegments.cs (1 hunks)
  • Deepgram/Clients/Interfaces/v2/IAgentWebSocketClient.cs (1 hunks)
  • Deepgram/Deepgram.csproj (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/Agent.cs (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/AgentAudioDoneResponse.cs (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/AgentStartedSpeakingResponse.cs (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/AgentThinkingResponse.cs (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/AgentType.cs (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/Audio.cs (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/AudioResponse.cs (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/CloseResponse.cs (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/Context.cs (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/ControlMessage.cs (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/ConversationTextResponse.cs (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/ErrorResponse.cs (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/Function.cs (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/FunctionCallRequestResponse.cs (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/FunctionCallResponseSchema.cs (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/FunctionCallingResponse.cs (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/Header.cs (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/InjectAgentMessageSchema.cs (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/InjectionRefusedResponse.cs (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/Input.cs (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/InstructionsUpdatedResponse.cs (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/Item.cs (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/KeepAliveSchema.cs (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/Listen.cs (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/OpenResponse.cs (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/Output.cs (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/Parameters.cs (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/Properties.cs (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/Provider.cs (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/SettingsAppliedResponse.cs (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/SettingsConfigurationSchema.cs (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/Speak.cs (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/SpeakUpdatedResponse.cs (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/Think.cs (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/UnhandledResponse.cs (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/UpdateInstructionsSchema.cs (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/UpdateSpeakSchema.cs (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/UserStartedSpeakingResponse.cs (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/WelcomeResponse.cs (1 hunks)
  • examples/agent/websocket/simple/Agent.csproj (1 hunks)
  • examples/agent/websocket/simple/Program.cs (1 hunks)
✅ Files skipped from review due to trivial changes (6)
  • examples/agent/websocket/simple/Agent.csproj
  • Deepgram/Models/Agent/v2/WebSocket/OpenResponse.cs
  • Deepgram/Models/Agent/v2/WebSocket/ErrorResponse.cs
  • Deepgram/Clients/Agent/v2/Websocket/Constants.cs
  • Deepgram/Models/Agent/v2/WebSocket/CloseResponse.cs
  • Deepgram/Models/Agent/v2/WebSocket/UnhandledResponse.cs
🧰 Additional context used
📓 Learnings (2)
Deepgram/Models/Agent/v2/WebSocket/ControlMessage.cs (1)
Learnt from: dvonthenen
PR: deepgram/deepgram-dotnet-sdk#316
File: Deepgram/Models/Speak/v1/WebSocket/ControlMessage.cs:7-8
Timestamp: 2024-11-12T11:33:12.821Z
Learning: Primary constructor syntax in class declarations, such as `public class ClassName(Type parameter)`, is valid starting from C# 12.
Deepgram/ClientFactory.cs (1)
Learnt from: dvonthenen
PR: deepgram/deepgram-dotnet-sdk#347
File: Deepgram/ClientFactory.cs:146-161
Timestamp: 2024-11-12T11:33:27.266Z
Learning: In `Deepgram/ClientFactory.cs`, the method `CreateListenWebSocketClient(int version, ...)` is intentionally returning `object` as a placeholder because there is only one version of the client. It should not be suggested to change the return type until multiple versions are implemented.
🪛 GitHub Actions: CI
Deepgram/Deepgram.csproj

[warning] Package 'System.Text.Json' 7.0.4 has a known high severity vulnerability, see: GHSA-hh2w-p6rv-4g7w

🔇 Additional comments (26)
Deepgram/Models/Agent/v2/WebSocket/UserStartedSpeakingResponse.cs (1)

1-5: LGTM! Well-structured file header.

The file includes proper copyright notice, license reference, and follows the correct namespace hierarchy.

Deepgram/Models/Agent/v2/WebSocket/InstructionsUpdatedResponse.cs (2)

1-4: LGTM! Well-structured license header.

The license header follows best practices by including copyright notice, license reference, and SPDX identifier.


5-5: LGTM! Properly structured namespace.

The namespace follows .NET conventions and matches the physical file structure.

Deepgram/Models/Agent/v2/WebSocket/AgentType.cs (2)

1-8: LGTM! Well-structured file header and namespace organization.

The file follows .NET conventions with proper license headers and namespace organization.


30-40: Add XML documentation and verify message type consistency.

The static class could benefit from better documentation and validation:

  1. Add XML documentation for the class and constants.
  2. Clarify the purpose of these message types in the comments.

Here's a suggested implementation:

+/// <summary>
+/// Defines the message types that can be sent by the client to the agent WebSocket API.
+/// </summary>
 public static class AgentClientTypes
 {
-    // user message types
+    // Client-to-agent message types for configuration and control
     /// <summary>
     /// Message type for configuring agent settings.
     /// </summary>
     public const string SettingsConfiguration = "SettingsConfiguration";
     // Add similar XML docs for other constants
 }

Let's verify that these message types match the API schema:

✅ Verification successful

Message types are well-defined and consistently used

The message types are properly integrated into the codebase with corresponding schema files and response models. Each constant has matching implementations in:

  • Schema files (e.g., SettingsConfigurationSchema.cs)
  • Response models (e.g., SettingsAppliedResponse.cs)
  • Client implementations across different versions

While XML documentation would improve the API's usability, the message types themselves are well-structured and consistently used.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Search for references to these message types in request/response models
# and schema definitions to ensure consistency.

echo "Searching for message type usage in models and schemas..."
rg -l "SettingsConfiguration|UpdateInstructions|UpdateSpeak|InjectAgentMessage|FunctionCallResponse|KeepAlive" \
  --type cs

echo -e "\nSearching for potential message type definitions in other files..."
rg -i "(?:const|static).*(message.*type|type.*message)" \
  --type cs

Length of output: 2391

Deepgram/Models/Agent/v2/WebSocket/Audio.cs (1)

18-24: Add null check in ToString() implementation.

The current implementation might throw NullReferenceException if JsonSerializeOptions.DefaultOptions is null.

Deepgram/Models/Agent/v2/WebSocket/Output.cs (2)

25-31: Add null check in ToString() implementation.

Similar to Audio.cs, the ToString implementation needs a null check for JsonSerializeOptions.DefaultOptions.


1-32: Add unit tests for the Output record.

The PR objectives mention that tests haven't been added yet. Consider adding unit tests to verify:

  1. Default value initialization
  2. JSON serialization/deserialization
  3. Null handling
  4. ToString() behavior

Would you like me to generate the unit test code?

Deepgram/Clients/Agent/v2/Websocket/Client.cs (2)

449-480: Consider using built-in CloseAsync or providing a WebSocket close status.
Calling SendAsync with a single null byte is an unorthodox way to close the connection. Usually, CloseAsync is used with a standard close status code. This approach may limit the ability to convey a proper close reason to the server or logs. Evaluate whether you can leverage the built-in close mechanism.


75-103: Ensure safe keep-alive lifecycle management.
Starting the keep-alive loop in a background thread without storing a reference or controlling when it should stop can create concurrency issues if the connection is closed soon after calling Connect. Consider tracking the loop’s Task so you can wait on it during shutdown or forcibly end it if errors occur.

Deepgram/Models/Agent/v2/WebSocket/Item.cs (1)

20-23: Consider removing Regex.Unescape from ToString.

The Regex.Unescape might not be necessary as JsonSerializer already handles proper JSON string escaping. This could potentially cause issues with valid JSON strings that contain escape sequences.

-        return Regex.Unescape(JsonSerializer.Serialize(this, JsonSerializeOptions.DefaultOptions));
+        return JsonSerializer.Serialize(this, JsonSerializeOptions.DefaultOptions);
Deepgram/Models/Agent/v2/WebSocket/Context.cs (1)

21-24: Consider removing Regex.Unescape from ToString.

Similar to Item.cs, the Regex.Unescape might not be necessary and could potentially cause issues.

-        return Regex.Unescape(JsonSerializer.Serialize(this, JsonSerializeOptions.DefaultOptions));
+        return JsonSerializer.Serialize(this, JsonSerializeOptions.DefaultOptions);
✅ Verification successful

Remove Regex.Unescape from ToString() in Content.cs

The Regex.Unescape is unnecessary as the properties don't contain special characters requiring unescaping. JsonSerializer.Serialize already handles proper JSON escaping. Note: The review mentions Context.cs but the file is actually Content.cs.

-        return Regex.Unescape(JsonSerializer.Serialize(this, JsonSerializeOptions.DefaultOptions));
+        return JsonSerializer.Serialize(this, JsonSerializeOptions.DefaultOptions);
🏁 Scripts executed

The following scripts were executed for the analysis:

Error: No shell scripts found


Script:

#!/bin/bash
# Find and show the complete Context.cs file
fd Context.cs | grep -i "websocket" | xargs cat

# Find and show Item.cs for comparison
fd Item.cs | grep -i "deepgram" | xargs cat

# Look for test files related to Context
fd "Context.*Test.*\.cs$"

# Search for usages of Context.ToString()
rg -l "Context.*ToString" --type cs

Length of output: 1763

Deepgram/Models/Agent/v2/WebSocket/SpeakUpdatedResponse.cs (1)

20-23: Consider removing Regex.Unescape from ToString.

Similar to other response classes, the Regex.Unescape might not be necessary and could potentially cause issues.

-        return Regex.Unescape(JsonSerializer.Serialize(this, JsonSerializeOptions.DefaultOptions));
+        return JsonSerializer.Serialize(this, JsonSerializeOptions.DefaultOptions);
Deepgram/Models/Agent/v2/WebSocket/AgentAudioDoneResponse.cs (1)

20-23: Consider removing Regex.Unescape from ToString.

Similar to other response classes, the Regex.Unescape might not be necessary and could potentially cause issues.

-        return Regex.Unescape(JsonSerializer.Serialize(this, JsonSerializeOptions.DefaultOptions));
+        return JsonSerializer.Serialize(this, JsonSerializeOptions.DefaultOptions);
Deepgram/Models/Agent/v2/WebSocket/InjectAgentMessageSchema.cs (2)

9-14: Fix incorrect XML documentation for Type property

Same issue as in UpdateSpeakSchema: The XML comment incorrectly states "SettingsConfiguration event type".


23-26: Add error handling to ToString method

Same issue as in UpdateSpeakSchema: The ToString method should handle potential serialization errors.

Deepgram/Models/Agent/v2/WebSocket/UpdateInstructionsSchema.cs (2)

9-14: Fix incorrect XML documentation for Type property

Same issue as in previous schemas: The XML comment incorrectly states "SettingsConfiguration event type".


23-26: Add error handling to ToString method

Same issue as in previous schemas: The ToString method should handle potential serialization errors.

Deepgram/Models/Agent/v2/WebSocket/WelcomeResponse.cs (3)

7-8: LGTM! Good use of record type

Using a record type is appropriate for this response object as it promotes immutability and provides value-based equality.


9-15: Fix incorrect XML documentation for Type property

Same issue as in previous schemas: The XML comment incorrectly states "SettingsConfiguration event type".


24-27: Add error handling to ToString method

Same issue as in previous schemas: The ToString method should handle potential serialization errors.

Deepgram/Models/Agent/v2/WebSocket/Parameters.cs (1)

9-11: Add XML documentation and consider making Type nullable.

The Type property is missing XML documentation and is not nullable despite having no default value.

+    /// <summary>
+    /// The type of the parameters.
+    /// </summary>
     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
     [JsonPropertyName("type")]
-    public string Type { get; set; }
+    public string? Type { get; set; }

Let's verify if the Type property can be null in the API response:

Deepgram/Clients/Interfaces/v2/IAgentWebSocketClient.cs (1)

126-133: Consider thread safety for non-async send methods.

The SendBinary and SendMessage methods are not async and don't accept cancellation tokens, which could lead to thread safety issues in a concurrent environment. Consider making them async or documenting thread safety guarantees.

Deepgram/ClientFactory.cs (1)

18-27: LGTM! The implementation follows the established pattern.

The new agent client factory methods are consistent with the existing codebase structure and patterns.

Deepgram.Dev.sln (1)

180-187: LGTM! Well-structured project organization.

The new agent-related projects are properly organized in the solution hierarchy with appropriate GUIDs and configurations.

Also applies to: 427-430

Deepgram/Deepgram.csproj (1)

127-131: LGTM! Clean removal of v1 agent models.

The removal of v1 agent models is appropriate as part of the transition to v2 implementation.

🧰 Tools
🪛 GitHub Actions: CI

[warning] Package 'System.Text.Json' 7.0.4 has a known high severity vulnerability, see: GHSA-hh2w-p6rv-4g7w

Comment on lines +7 to +15
public record UserStartedSpeakingResponse
{
/// <summary>
/// SettingsConfiguration event type.
/// </summary>
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[JsonPropertyName("type")]
[JsonConverter(typeof(JsonStringEnumConverter))]
public AgentType? Type { get; } = AgentType.UserStartedSpeaking;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add proper documentation and consider property design.

  1. The XML documentation for the Type property appears to be incorrect (mentions "SettingsConfiguration").
  2. Consider adding XML documentation for the record class itself.
  3. Since Type has a default value of AgentType.UserStartedSpeaking, making it nullable might be unnecessary.

Apply these changes:

+/// <summary>
+/// Represents a response indicating that a user has started speaking in an agent conversation.
+/// </summary>
 public record UserStartedSpeakingResponse
 {
     /// <summary>
-    /// SettingsConfiguration event type.
+    /// The type of agent event, which is always UserStartedSpeaking for this response.
     /// </summary>
     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
     [JsonPropertyName("type")]
     [JsonConverter(typeof(JsonStringEnumConverter))]
-    public AgentType? Type { get; } = AgentType.UserStartedSpeaking;
+    public AgentType Type { get; } = AgentType.UserStartedSpeaking;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public record UserStartedSpeakingResponse
{
/// <summary>
/// SettingsConfiguration event type.
/// </summary>
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[JsonPropertyName("type")]
[JsonConverter(typeof(JsonStringEnumConverter))]
public AgentType? Type { get; } = AgentType.UserStartedSpeaking;
/// <summary>
/// Represents a response indicating that a user has started speaking in an agent conversation.
/// </summary>
public record UserStartedSpeakingResponse
{
/// <summary>
/// The type of agent event, which is always UserStartedSpeaking for this response.
/// </summary>
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[JsonPropertyName("type")]
[JsonConverter(typeof(JsonStringEnumConverter))]
public AgentType Type { get; } = AgentType.UserStartedSpeaking;

Comment on lines +17 to +23
/// <summary>
/// Override ToString method to serialize the object
/// </summary>
public override string ToString()
{
return Regex.Unescape(JsonSerializer.Serialize(this, JsonSerializeOptions.DefaultOptions));
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve ToString implementation and documentation.

  1. Missing using directives for required types.
  2. Using Regex.Unescape might be unnecessary and could potentially cause issues with valid JSON strings.
  3. Incomplete XML documentation.

Apply these changes:

+using System.Text.Json;
+using System.Text.RegularExpressions;

     /// <summary>
-    /// Override ToString method to serialize the object
+    /// Converts the current object to its JSON string representation.
     /// </summary>
+    /// <returns>A JSON string representation of the current object.</returns>
     public override string ToString()
     {
-        return Regex.Unescape(JsonSerializer.Serialize(this, JsonSerializeOptions.DefaultOptions));
+        return JsonSerializer.Serialize(this, JsonSerializeOptions.DefaultOptions);
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/// <summary>
/// Override ToString method to serialize the object
/// </summary>
public override string ToString()
{
return Regex.Unescape(JsonSerializer.Serialize(this, JsonSerializeOptions.DefaultOptions));
}
using System.Text.Json;
using System.Text.RegularExpressions;
/// <summary>
/// Converts the current object to its JSON string representation.
/// </summary>
/// <returns>A JSON string representation of the current object.</returns>
public override string ToString()
{
return JsonSerializer.Serialize(this, JsonSerializeOptions.DefaultOptions);
}

Comment on lines +7 to +16
public record InstructionsUpdatedResponse
{
/// <summary>
/// SettingsConfiguration event type.
/// </summary>
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[JsonPropertyName("type")]
[JsonConverter(typeof(JsonStringEnumConverter))]
public AgentType? Type { get; } = AgentType.InstructionsUpdated;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Fix documentation and property nullability.

  1. The XML documentation for the Type property is incorrect (mentions "SettingsConfiguration" instead of "Instructions Updated").
  2. Missing XML documentation for the record class itself.
  3. The Type property could be non-nullable since it has a default value.

Apply these changes:

+/// <summary>
+/// Represents a response indicating that agent instructions have been updated.
+/// </summary>
 public record InstructionsUpdatedResponse
 {
     /// <summary>
-    /// SettingsConfiguration event type.
+    /// The type of agent event, which is always InstructionsUpdated.
     /// </summary>
     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
     [JsonPropertyName("type")]
     [JsonConverter(typeof(JsonStringEnumConverter))]
-    public AgentType? Type { get; } = AgentType.InstructionsUpdated;
+    public AgentType Type { get; } = AgentType.InstructionsUpdated;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public record InstructionsUpdatedResponse
{
/// <summary>
/// SettingsConfiguration event type.
/// </summary>
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[JsonPropertyName("type")]
[JsonConverter(typeof(JsonStringEnumConverter))]
public AgentType? Type { get; } = AgentType.InstructionsUpdated;
/// <summary>
/// Represents a response indicating that agent instructions have been updated.
/// </summary>
public record InstructionsUpdatedResponse
{
/// <summary>
/// The type of agent event, which is always InstructionsUpdated.
/// </summary>
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[JsonPropertyName("type")]
[JsonConverter(typeof(JsonStringEnumConverter))]
public AgentType Type { get; } = AgentType.InstructionsUpdated;

Comment on lines +51 to +53
public async Task<bool> Connect(SettingsConfigurationSchema options, CancellationTokenSource? cancelToken = null, Dictionary<string, string>? addons = null,
Dictionary<string, string>? headers = null)
{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Use a CancellationToken parameter instead of CancellationTokenSource.
Passing a CancellationTokenSource to the Connect method exposes lower-level management details to callers. Typically, methods accept a CancellationToken, allowing the caller to manage the CancellationTokenSource more flexibly and handle cancellation at a higher level.

-public async Task<bool> Connect(SettingsConfigurationSchema options, CancellationTokenSource? cancelToken = null, Dictionary<string, string>? addons = null,
-    Dictionary<string, string>? headers = null)
+public async Task<bool> Connect(
+    SettingsConfigurationSchema options,
+    CancellationToken cancelToken = default,
+    Dictionary<string, string>? addons = null,
+    Dictionary<string, string>? headers = null)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public async Task<bool> Connect(SettingsConfigurationSchema options, CancellationTokenSource? cancelToken = null, Dictionary<string, string>? addons = null,
Dictionary<string, string>? headers = null)
{
public async Task<bool> Connect(
SettingsConfigurationSchema options,
CancellationToken cancelToken = default,
Dictionary<string, string>? addons = null,
Dictionary<string, string>? headers = null)
{

Comment on lines +7 to +14
public class ControlMessage(string text)
{
/// <summary>
/// Gets or sets the type of control message.
/// </summary>
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[JsonPropertyName("type")]
public string? Type { get; set; } = text;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider using a record and add parameter validation.

  1. Add validation for the text parameter
  2. Consider making this a record with init-only property
  3. Add XML documentation for the constructor
- public class ControlMessage(string text)
+ /// <summary>
+ /// Represents a control message in the WebSocket communication.
+ /// </summary>
+ /// <param name="text">The type of control message. Cannot be null.</param>
+ /// <exception cref="ArgumentNullException">Thrown when text is null.</exception>
+ public record ControlMessage
 {
+    public ControlMessage(string text)
+    {
+        ArgumentNullException.ThrowIfNull(text);
+        Type = text;
+    }
+
     /// <summary>
     /// Gets or sets the type of control message.
     /// </summary>
     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
     [JsonPropertyName("type")]
-    public string? Type { get; set; } = text;
+    public string? Type { get; init; }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public class ControlMessage(string text)
{
/// <summary>
/// Gets or sets the type of control message.
/// </summary>
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[JsonPropertyName("type")]
public string? Type { get; set; } = text;
/// <summary>
/// Represents a control message in the WebSocket communication.
/// </summary>
/// <param name="text">The type of control message. Cannot be null.</param>
/// <exception cref="ArgumentNullException">Thrown when text is null.</exception>
public record ControlMessage
{
public ControlMessage(string text)
{
ArgumentNullException.ThrowIfNull(text);
Type = text;
}
/// <summary>
/// Gets or sets the type of control message.
/// </summary>
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[JsonPropertyName("type")]
public string? Type { get; init; }

Comment on lines +127 to +186
await agentClient.Subscribe(new EventHandler<AgentAudioDoneResponse>((sender, e) =>
{
Console.WriteLine($"----> {e} received");
}));
await agentClient.Subscribe(new EventHandler<AgentStartedSpeakingResponse>((sender, e) =>
{
Console.WriteLine($"----> {e} received");
}));
await agentClient.Subscribe(new EventHandler<AgentThinkingResponse>((sender, e) =>
{
Console.WriteLine($"----> {e} received");
}));
await agentClient.Subscribe(new EventHandler<ConversationTextResponse>((sender, e) =>
{
Console.WriteLine($"----> {e} received");
}));
await agentClient.Subscribe(new EventHandler<FunctionCallingResponse>((sender, e) =>
{
Console.WriteLine($"----> {e} received");
}));
await agentClient.Subscribe(new EventHandler<FunctionCallRequestResponse>((sender, e) =>
{
Console.WriteLine($"----> {e} received");
}));
await agentClient.Subscribe(new EventHandler<UserStartedSpeakingResponse>((sender, e) =>
{
Console.WriteLine($"----> {e} received");
}));
await agentClient.Subscribe(new EventHandler<WelcomeResponse>((sender, e) =>
{
Console.WriteLine($"----> {e} received");
}));
await agentClient.Subscribe(new EventHandler<CloseResponse>((sender, e) =>
{
Console.WriteLine($"----> {e} received");
}));
await agentClient.Subscribe(new EventHandler<SettingsAppliedResponse>((sender, e) =>
{
Console.WriteLine($"----> {e} received");
}));
await agentClient.Subscribe(new EventHandler<InjectionRefusedResponse>((sender, e) =>
{
Console.WriteLine($"----> {e} received");
}));
await agentClient.Subscribe(new EventHandler<InstructionsUpdatedResponse>((sender, e) =>
{
Console.WriteLine($"----> {e} received");
}));
await agentClient.Subscribe(new EventHandler<SpeakUpdatedResponse>((sender, e) =>
{
Console.WriteLine($"----> {e} received.");
}));
await agentClient.Subscribe(new EventHandler<UnhandledResponse>((sender, e) =>
{
Console.WriteLine($"----> {e} received");
}));
await agentClient.Subscribe(new EventHandler<ErrorResponse>((sender, e) =>
{
Console.WriteLine($"----> {e} received. Error: {e.Message}");
}));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Enhance event handlers with proper error handling and logging.

The current event handlers only log the event type. Consider adding:

  • Proper error handling
  • Detailed logging of event data
  • Retry logic for recoverable errors

Example enhancement for one handler:

await agentClient.Subscribe(new EventHandler<AgentAudioDoneResponse>((sender, e) =>
{
-    Console.WriteLine($"----> {e} received");
+    try 
+    {
+        Console.WriteLine($"----> Agent audio done event received");
+        Console.WriteLine($"      Duration: {e.Duration}");
+        Console.WriteLine($"      Status: {e.Status}");
+    }
+    catch (Exception ex)
+    {
+        Console.WriteLine($"Error handling agent audio done event: {ex.Message}");
+    }
}));

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +196 to +201
bool bConnected = await agentClient.Connect(settingsConfiguration);
if (!bConnected)
{
Console.WriteLine("Failed to connect to Deepgram WebSocket server.");
return;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Enhance connection error handling.

The connection failure handling is minimal. Consider adding retry logic and more detailed error information.

-bool bConnected = await agentClient.Connect(settingsConfiguration);
-if (!bConnected)
-{
-    Console.WriteLine("Failed to connect to Deepgram WebSocket server.");
-    return;
-}
+int maxRetries = 3;
+for (int i = 0; i < maxRetries; i++)
+{
+    try
+    {
+        bool bConnected = await agentClient.Connect(settingsConfiguration);
+        if (bConnected)
+        {
+            Console.WriteLine("Successfully connected to Deepgram WebSocket server.");
+            break;
+        }
+        
+        if (i == maxRetries - 1)
+        {
+            throw new Exception("Maximum retry attempts reached");
+        }
+        
+        Console.WriteLine($"Connection attempt {i + 1} failed, retrying...");
+        await Task.Delay(1000 * (i + 1));  // Exponential backoff
+    }
+    catch (Exception ex)
+    {
+        Console.WriteLine($"Failed to connect: {ex.Message}");
+        return;
+    }
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
bool bConnected = await agentClient.Connect(settingsConfiguration);
if (!bConnected)
{
Console.WriteLine("Failed to connect to Deepgram WebSocket server.");
return;
}
int maxRetries = 3;
for (int i = 0; i < maxRetries; i++)
{
try
{
bool bConnected = await agentClient.Connect(settingsConfiguration);
if (bConnected)
{
Console.WriteLine("Successfully connected to Deepgram WebSocket server.");
break;
}
if (i == maxRetries - 1)
{
throw new Exception("Maximum retry attempts reached");
}
Console.WriteLine($"Connection attempt {i + 1} failed, retrying...");
await Task.Delay(1000 * (i + 1)); // Exponential backoff
}
catch (Exception ex)
{
Console.WriteLine($"Failed to connect: {ex.Message}");
return;
}
}

Comment on lines +51 to +55
// delete the file if it exists
if (File.Exists($"output_{audioFileCount}.wav"))
{
File.Delete($"output_{audioFileCount}.wav");
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add directory cleanup and exception handling for file operations.

File operations should be wrapped in try-catch blocks and old files should be cleaned up.

+try
+{
+    // Clean up old files
+    var oldFiles = Directory.GetFiles(".", "output_*.wav");
+    foreach (var file in oldFiles.OrderByDescending(f => f))
+    {
+        if (int.Parse(Path.GetFileNameWithoutExtension(file).Split('_')[1]) < audioFileCount - 5)
+        {
+            File.Delete(file);
+        }
+    }
+
     // delete the file if it exists
     if (File.Exists($"output_{audioFileCount}.wav"))
     {
         File.Delete($"output_{audioFileCount}.wav");
     }
+}
+catch (Exception ex)
+{
+    Console.WriteLine($"Error managing audio files: {ex.Message}");
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// delete the file if it exists
if (File.Exists($"output_{audioFileCount}.wav"))
{
File.Delete($"output_{audioFileCount}.wav");
}
try
{
// Clean up old files
var oldFiles = Directory.GetFiles(".", "output_*.wav");
foreach (var file in oldFiles.OrderByDescending(f => f))
{
if (int.Parse(Path.GetFileNameWithoutExtension(file).Split('_')[1]) < audioFileCount - 5)
{
File.Delete(file);
}
}
// delete the file if it exists
if (File.Exists($"output_{audioFileCount}.wav"))
{
File.Delete($"output_{audioFileCount}.wav");
}
}
catch (Exception ex)
{
Console.WriteLine($"Error managing audio files: {ex.Message}");
}

// Start the connection
var settingsConfiguration = new SettingsConfigurationSchema();
settingsConfiguration.Agent.Think.Provider.Type = "open_ai";
settingsConfiguration.Agent.Think.Model = "gpt-4o-mini";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Invalid OpenAI model name "gpt-4o-mini"

The model name appears to be a typo. OpenAI doesn't have a model named "gpt-4o-mini". The standard OpenAI model names follow patterns like "gpt-4", "gpt-4-turbo", or "gpt-3.5-turbo". This will cause runtime errors when attempting to use the OpenAI provider.

🔗 Analysis chain

Verify the model name "gpt-4o-mini".

The model name appears to be incorrect. Verify if this is a typo or if this model actually exists.

Run this script to check available models:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check if the model exists in the codebase or documentation
rg -i "gpt-4o-mini"

Length of output: 135


Script:

#!/bin/bash
# Search for model configurations in the codebase
rg -i "model.*=.*gpt" -A 1 -B 1

Length of output: 412

@@ -123,6 +123,12 @@
<PackageReference Include="System.Text.Json" Version="8.*" />
<PackageReference Include="System.Threading.Channels" Version="8.*" />
</ItemGroup>

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Address high severity vulnerability in System.Text.Json package.

The pipeline detected a high severity vulnerability in System.Text.Json 7.0.4.

Update the package references to use patched versions:

-    <PackageReference Include="System.Text.Json" Version="7.*" />
+    <PackageReference Include="System.Text.Json" Version="7.0.5" />

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 GitHub Actions: CI

[warning] Package 'System.Text.Json' 7.0.4 has a known high severity vulnerability, see: GHSA-hh2w-p6rv-4g7w

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (5)
examples/agent/websocket/simple/Program.cs (5)

30-31: ⚠️ Potential issue

Add API key validation and error handling.

The code passes an empty API key to CreateAgentWebSocketClient. This will cause runtime issues.


51-55: 🛠️ Refactor suggestion

Add directory cleanup and exception handling for file operations.

File operations should be wrapped in try-catch blocks and old files should be cleaned up.


127-186: 🛠️ Refactor suggestion

Enhance event handlers with proper error handling and logging.

The current event handlers only log the event type. Consider adding proper error handling, detailed logging of event data, and retry logic for recoverable errors.


191-191: ⚠️ Potential issue

Invalid OpenAI model name "gpt-4o-mini".

The model name appears to be a typo. OpenAI doesn't have a model named "gpt-4o-mini".


196-201: 🛠️ Refactor suggestion

Enhance connection error handling.

The connection failure handling is minimal. Consider adding retry logic and more detailed error information.

🧹 Nitpick comments (4)
Deepgram/Clients/Agent/v2/Websocket/Client.cs (4)

51-53: Unused parameter addons
The addons parameter here is never used in the method. If this is intentional, consider removing it or adding documentation explaining why it exists.


105-434: Unify base subscription vs. local event subscription
Not all subscribe methods call base.Subscribe; some attach directly to private event fields, while others rely on the base call. This discontinuity complicates event unsubscription and may lead to inconsistent code paths.


577-577: Dispose parsed JsonDocument
JsonDocument is disposable. Wrapping it with a using var data = JsonDocument.Parse(response); ensures proper disposal for improved memory management.


849-855: Compile the Regex for better performance
Using new Regex(pattern, RegexOptions.Compiled) can provide performance benefits if the same pattern is used multiple times. Though minor for single usage, it’s a simple improvement.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 23e95af and fba9367.

📒 Files selected for processing (50)
  • Deepgram.Dev.sln (3 hunks)
  • Deepgram/AgentWebSocketClient.cs (1 hunks)
  • Deepgram/ClientFactory.cs (2 hunks)
  • Deepgram/Clients/Agent/v2/Websocket/Client.cs (1 hunks)
  • Deepgram/Clients/Agent/v2/Websocket/Constants.cs (1 hunks)
  • Deepgram/Clients/Agent/v2/Websocket/ResponseEvent.cs (1 hunks)
  • Deepgram/Clients/Agent/v2/Websocket/UriSegments.cs (1 hunks)
  • Deepgram/Clients/Interfaces/v2/IAgentWebSocketClient.cs (1 hunks)
  • Deepgram/Deepgram.csproj (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/Agent.cs (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/AgentAudioDoneResponse.cs (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/AgentStartedSpeakingResponse.cs (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/AgentThinkingResponse.cs (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/AgentType.cs (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/Audio.cs (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/AudioResponse.cs (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/CloseResponse.cs (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/Context.cs (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/ControlMessage.cs (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/ConversationTextResponse.cs (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/ErrorResponse.cs (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/Function.cs (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/FunctionCallRequestResponse.cs (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/FunctionCallResponseSchema.cs (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/FunctionCallingResponse.cs (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/Header.cs (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/InjectAgentMessageSchema.cs (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/InjectionRefusedResponse.cs (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/Input.cs (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/InstructionsUpdatedResponse.cs (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/Item.cs (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/KeepAliveSchema.cs (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/Listen.cs (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/OpenResponse.cs (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/Output.cs (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/Parameters.cs (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/Properties.cs (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/Provider.cs (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/SettingsAppliedResponse.cs (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/SettingsConfigurationSchema.cs (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/Speak.cs (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/SpeakUpdatedResponse.cs (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/Think.cs (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/UnhandledResponse.cs (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/UpdateInstructionsSchema.cs (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/UpdateSpeakSchema.cs (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/UserStartedSpeakingResponse.cs (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/WelcomeResponse.cs (1 hunks)
  • examples/agent/websocket/simple/Agent.csproj (1 hunks)
  • examples/agent/websocket/simple/Program.cs (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (44)
  • Deepgram/Deepgram.csproj
  • Deepgram/Models/Agent/v2/WebSocket/UnhandledResponse.cs
  • Deepgram/Models/Agent/v2/WebSocket/ErrorResponse.cs
  • Deepgram/Models/Agent/v2/WebSocket/OpenResponse.cs
  • Deepgram/Models/Agent/v2/WebSocket/CloseResponse.cs
  • Deepgram/Models/Agent/v2/WebSocket/SettingsAppliedResponse.cs
  • Deepgram/Models/Agent/v2/WebSocket/Speak.cs
  • Deepgram/Models/Agent/v2/WebSocket/UserStartedSpeakingResponse.cs
  • Deepgram/AgentWebSocketClient.cs
  • examples/agent/websocket/simple/Agent.csproj
  • Deepgram/Models/Agent/v2/WebSocket/ControlMessage.cs
  • Deepgram/Models/Agent/v2/WebSocket/KeepAliveSchema.cs
  • Deepgram/Models/Agent/v2/WebSocket/Properties.cs
  • Deepgram/Clients/Agent/v2/Websocket/ResponseEvent.cs
  • Deepgram/Models/Agent/v2/WebSocket/FunctionCallRequestResponse.cs
  • Deepgram/Clients/Agent/v2/Websocket/Constants.cs
  • Deepgram/Models/Agent/v2/WebSocket/FunctionCallResponseSchema.cs
  • Deepgram/Models/Agent/v2/WebSocket/UpdateInstructionsSchema.cs
  • Deepgram/Models/Agent/v2/WebSocket/AgentAudioDoneResponse.cs
  • Deepgram/Clients/Agent/v2/Websocket/UriSegments.cs
  • Deepgram/Models/Agent/v2/WebSocket/SpeakUpdatedResponse.cs
  • Deepgram/Models/Agent/v2/WebSocket/UpdateSpeakSchema.cs
  • Deepgram/Models/Agent/v2/WebSocket/Provider.cs
  • Deepgram/Models/Agent/v2/WebSocket/AgentStartedSpeakingResponse.cs
  • Deepgram/Models/Agent/v2/WebSocket/Listen.cs
  • Deepgram/Models/Agent/v2/WebSocket/FunctionCallingResponse.cs
  • Deepgram/Models/Agent/v2/WebSocket/InstructionsUpdatedResponse.cs
  • Deepgram/Models/Agent/v2/WebSocket/WelcomeResponse.cs
  • Deepgram/Models/Agent/v2/WebSocket/Item.cs
  • Deepgram/Models/Agent/v2/WebSocket/Audio.cs
  • Deepgram/Models/Agent/v2/WebSocket/Output.cs
  • Deepgram/Models/Agent/v2/WebSocket/SettingsConfigurationSchema.cs
  • Deepgram/Models/Agent/v2/WebSocket/Think.cs
  • Deepgram/Models/Agent/v2/WebSocket/AudioResponse.cs
  • Deepgram/Models/Agent/v2/WebSocket/InjectionRefusedResponse.cs
  • Deepgram/Models/Agent/v2/WebSocket/ConversationTextResponse.cs
  • Deepgram/Models/Agent/v2/WebSocket/Context.cs
  • Deepgram/Models/Agent/v2/WebSocket/Header.cs
  • Deepgram/Models/Agent/v2/WebSocket/AgentThinkingResponse.cs
  • Deepgram/Models/Agent/v2/WebSocket/Agent.cs
  • Deepgram/Models/Agent/v2/WebSocket/Input.cs
  • Deepgram/Models/Agent/v2/WebSocket/Function.cs
  • Deepgram.Dev.sln
  • Deepgram/Clients/Interfaces/v2/IAgentWebSocketClient.cs
🧰 Additional context used
📓 Learnings (1)
Deepgram/ClientFactory.cs (1)
Learnt from: dvonthenen
PR: deepgram/deepgram-dotnet-sdk#347
File: Deepgram/ClientFactory.cs:146-161
Timestamp: 2024-11-12T11:33:27.266Z
Learning: In `Deepgram/ClientFactory.cs`, the method `CreateListenWebSocketClient(int version, ...)` is intentionally returning `object` as a placeholder because there is only one version of the client. It should not be suggested to change the return type until multiple versions are implemented.
🔇 Additional comments (7)
Deepgram/Clients/Agent/v2/Websocket/Client.cs (1)

51-53: Consider switching from CancellationTokenSource to CancellationToken
This echoes a prior suggestion (lines 51-53) that remains unaddressed. Accepting a CancellationToken instead of a CancellationTokenSource heightens flexibility for callers and aligns with .NET best practices.

Deepgram/Models/Agent/v2/WebSocket/InjectAgentMessageSchema.cs (1)

23-26: Verify necessity of Regex.Unescape
Regex.Unescape in ToString() might lead to unexpected transformations if the serialized JSON contains escape sequences like “\n”. Confirm this behavior suits your needs.

Deepgram/Models/Agent/v2/WebSocket/Parameters.cs (1)

26-29: Ask for confirmation of Regex.Unescape usage
Similar to other classes, the call to Regex.Unescape can alter valid escape sequences. Consider double-checking its necessity if the output must remain raw JSON.

Deepgram/Models/Agent/v2/WebSocket/AgentType.cs (2)

9-28: LGTM! Well-structured enum definition.

The AgentType enum is well-organized, extending WebSocketType with agent-specific states in a logical flow from connection states to various agent interaction states.


30-40: LGTM! Clear and consistent constant definitions.

The AgentClientTypes class provides a clean set of constants for message types with consistent naming conventions.

Deepgram/ClientFactory.cs (2)

18-27: LGTM! Well-documented client creation method.

The implementation follows the established pattern for creating latest version clients, with proper XML documentation and type-safe return value.


122-129: LGTM! Consistent with codebase patterns.

The versioned method correctly follows the codebase pattern of returning object as a placeholder until multiple versions are implemented.

@jpvajda jpvajda self-requested a review January 29, 2025 17:24
@jpvajda
Copy link
Contributor

jpvajda commented Jan 29, 2025

@naomi-lgbt we'll want to add a link reference to the Readme to highlight the agent examples see this section: https://github.com/deepgram/deepgram-dotnet-sdk/tree/feat/agent?tab=readme-ov-file#example-code

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
Deepgram/Clients/Interfaces/v2/IAgentWebSocketClient.cs (1)

143-143: ⚠️ Potential issue

Fix inconsistent parameter naming.

The _cancellationToken parameter name starts with an underscore, which is inconsistent with C# naming conventions and other parameters in the interface.

Also applies to: 165-165, 173-173

🧹 Nitpick comments (3)
Deepgram/Clients/Interfaces/v2/IAgentWebSocketClient.cs (3)

9-12: Fix interface documentation.

The XML comment incorrectly states that the interface "Implements" version 2. Interfaces define or specify contracts rather than implement them.

-/// Implements version 2 of the Agent Client.
+/// Defines the contract for version 2 of the Agent Client.

18-19: Consider reordering parameters for better usability.

The Connect method's parameter order could be improved by grouping required parameters first, followed by optional parameters.

-public Task<bool> Connect(SettingsConfigurationSchema options, CancellationTokenSource? cancelToken = null, Dictionary<string, string>? addons = null,
-    Dictionary<string, string>? headers = null);
+public Task<bool> Connect(
+    SettingsConfigurationSchema options,
+    Dictionary<string, string>? headers = null,
+    Dictionary<string, string>? addons = null,
+    CancellationTokenSource? cancelToken = null);

12-13: Add thread safety and exception documentation.

Consider adding interface-level documentation about:

  1. Thread safety guarantees
  2. Common exceptions that might be thrown
  3. Any specific implementation requirements

This will help implementers better understand the contract requirements.

 /// <summary>
 /// Defines the contract for version 2 of the Agent Client.
+/// </summary>
+/// <remarks>
+/// Implementations of this interface should be thread-safe.
+/// Common exceptions that may be thrown include:
+/// <list type="bullet">
+/// <item><description>InvalidOperationException when operations are called in invalid states</description></item>
+/// <item><description>WebSocketException for connection-related issues</description></item>
+/// </list>
 /// </summary>
 public interface IAgentWebSocketClient
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fba9367 and eb8c0a3.

📒 Files selected for processing (1)
  • Deepgram/Clients/Interfaces/v2/IAgentWebSocketClient.cs (1 hunks)
🔇 Additional comments (2)
Deepgram/Clients/Interfaces/v2/IAgentWebSocketClient.cs (2)

1-8: LGTM!

The file header follows best practices with proper copyright notice, license information, and namespace organization.


176-188: LGTM!

The helper methods are well-documented and provide clear functionality for checking the WebSocket connection state.

Deepgram/Clients/Interfaces/v2/IAgentWebSocketClient.cs Outdated Show resolved Hide resolved
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (1)
Deepgram/Models/Agent/v2/WebSocket/Context.cs (1)

7-16: 🛠️ Refactor suggestion

Consider using a more specific type than List.

Using List<object> loses type safety and makes it harder to work with the messages. Consider creating a specific type for messages or using a more specific base type.

Additionally, please add XML documentation comments for the properties to describe their purpose and expected values:

+    /// <summary>
+    /// Gets or sets the list of messages in the context.
+    /// </summary>
     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
     [JsonPropertyName("messages")]
     public List<object>? Messages { get; set; }

+    /// <summary>
+    /// Gets or sets a value indicating whether to replay the context.
+    /// </summary>
     [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
     [JsonPropertyName("replay")]
     public bool? Replay { get; set; }
🧹 Nitpick comments (3)
Deepgram/Models/Agent/v2/WebSocket/Context.cs (1)

18-24: Improve error handling in ToString method.

The current implementation doesn't handle serialization errors. Consider adding try-catch block:

     public override string ToString()
     {
-        return Regex.Unescape(JsonSerializer.Serialize(this, JsonSerializeOptions.DefaultOptions));
+        try
+        {
+            return Regex.Unescape(JsonSerializer.Serialize(this, JsonSerializeOptions.DefaultOptions));
+        }
+        catch (JsonException ex)
+        {
+            return $"Error serializing {nameof(Context)}: {ex.Message}";
+        }
     }
examples/agent/websocket/simple/Program.cs (2)

51-55: Use Path.Combine for file paths and add file access error handling.

File operations should use Path.Combine for path construction and include proper error handling.

-if (File.Exists($"output_{audioFileCount}.wav"))
+var outputPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, $"output_{audioFileCount}.wav");
+if (File.Exists(outputPath))
 {
-    File.Delete($"output_{audioFileCount}.wav");
+    try
+    {
+        File.Delete(outputPath);
+    }
+    catch (IOException ex)
+    {
+        Console.WriteLine($"Error deleting file: {ex.Message}");
+        return;
+    }
 }

Also applies to: 118-122


38-186: Improve event handler organization and logging.

The event handlers could be better organized and include more meaningful logging:

  1. Group related handlers together (e.g., agent events, audio events)
  2. Add type-specific information to logs

Example for one group of handlers:

+// Agent state events
 await agentClient.Subscribe(new EventHandler<AgentStartedSpeakingResponse>((sender, e) =>
 {
-    Console.WriteLine($"----> {e} received");
+    Console.WriteLine($"Agent started speaking: SessionId={e.SessionId}");
 }));
 await agentClient.Subscribe(new EventHandler<AgentThinkingResponse>((sender, e) =>
 {
-    Console.WriteLine($"----> {e} received");
+    Console.WriteLine($"Agent thinking: SessionId={e.SessionId}");
 }));
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between eb8c0a3 and 4242619.

📒 Files selected for processing (4)
  • .gitignore (1 hunks)
  • Deepgram/Models/Agent/v2/WebSocket/Context.cs (1 hunks)
  • examples/agent/websocket/simple/Agent.csproj (1 hunks)
  • examples/agent/websocket/simple/Program.cs (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • .gitignore
🔇 Additional comments (7)
examples/agent/websocket/simple/Agent.csproj (3)

1-8: LGTM! Good project configuration choices.

The project is well-configured with:

  • Latest .NET 8.0 LTS version
  • Nullable reference types for better type safety
  • Implicit usings for cleaner code

15-17: LGTM! Appropriate namespace configuration.

The global using directive for the Deepgram namespace will help reduce code duplication across files.


10-13: Verify project reference paths.

The project references use deep relative paths (..\..\..\..\). While functional, this could be fragile if the project structure changes.

Run this script to verify the project structure:

✅ Verification successful

Project references are correctly structured

The referenced projects exist at the expected locations and the relative paths are correct for the current project structure. While the paths use multiple parent directory traversals, this is consistent with the repository's organization of example projects.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify project references exist and check for alternative path options

# Check if referenced projects exist
fd -e csproj -x echo "Found project: {}"

# Look for .sln files to understand solution structure
fd -e sln -x echo "Found solution: {}"

Length of output: 2635

Deepgram/Models/Agent/v2/WebSocket/Context.cs (1)

7-25: Verify the usage of JsonSerializeOptions.DefaultOptions.

Let's verify that JsonSerializeOptions.DefaultOptions is properly defined and accessible.

✅ Verification successful

JsonSerializeOptions.DefaultOptions is properly defined and widely used

The static property is correctly implemented in the internal JsonSerializeOptions class, providing consistent serialization settings across the codebase with web-compatible defaults and indented formatting.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for JsonSerializeOptions class definition and its DefaultOptions property
ast-grep --pattern 'class JsonSerializeOptions {
  $$$
  static $_DefaultOptions$_ {
    $$$
  }
  $$$
}'

Length of output: 107


Script:

#!/bin/bash
# Search for JsonSerializeOptions in all files
echo "=== Searching for JsonSerializeOptions ==="
rg -l "JsonSerializeOptions"

echo -e "\n=== Searching for JsonSerializeOptions with context ==="
rg "JsonSerializeOptions" -A 5 -B 5

echo -e "\n=== Searching for DefaultOptions in .cs files ==="
rg "DefaultOptions" --type csharp

Length of output: 67888

examples/agent/websocket/simple/Program.cs (3)

29-31: Add API key validation and error handling.

The code passes an empty API key to CreateAgentWebSocketClient. This will likely cause runtime issues when attempting to connect to the Deepgram API.


191-191: Invalid OpenAI model name "gpt-4o-mini".

The model name appears to be a typo. OpenAI doesn't have a model named "gpt-4o-mini".


198-203: Enhance connection error handling.

The connection failure handling is minimal. Consider adding retry logic and more detailed error information.

Comment on lines +1 to +6
// Copyright 2024 Deepgram .NET SDK contributors. All Rights Reserved.
// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
// SPDX-License-Identifier: MIT

namespace Deepgram.Models.Agent.v2.WebSocket;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add missing using directives.

The following using directives are required:

+using System.Text.RegularExpressions;
+using System.Text.Json;
+using System.Text.Json.Serialization;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Copyright 2024 Deepgram .NET SDK contributors. All Rights Reserved.
// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
// SPDX-License-Identifier: MIT
namespace Deepgram.Models.Agent.v2.WebSocket;
// Copyright 2024 Deepgram .NET SDK contributors. All Rights Reserved.
// Use of this source code is governed by a MIT license that can be found in the LICENSE file.
// SPDX-License-Identifier: MIT
using System.Text.RegularExpressions;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace Deepgram.Models.Agent.v2.WebSocket;

Comment on lines +205 to +214
// Microphone streaming
var microphone = new Microphone(agentClient.SendBinary);
microphone.Start();

// Wait for the user to press a key
Console.ReadKey();

// Stop the microphone
microphone.Stop();

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve microphone resource management.

The microphone should be properly disposed of to ensure resources are cleaned up.

-var microphone = new Microphone(agentClient.SendBinary);
-microphone.Start();
+using var microphone = new Microphone(agentClient.SendBinary);
+try
+{
+    microphone.Start();
+    // Wait for the user to press a key
+    Console.ReadKey();
+}
+finally
+{
+    microphone.Stop();
+}
-// Wait for the user to press a key
-Console.ReadKey();
-
-// Stop the microphone
-microphone.Stop();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Microphone streaming
var microphone = new Microphone(agentClient.SendBinary);
microphone.Start();
// Wait for the user to press a key
Console.ReadKey();
// Stop the microphone
microphone.Stop();
// Microphone streaming
using var microphone = new Microphone(agentClient.SendBinary);
try
{
microphone.Start();
// Wait for the user to press a key
Console.ReadKey();
}
finally
{
microphone.Stop();
}

Comment on lines +16 to +26
try
{
// Initialize Library with default logging
// Normal logging is "Info" level
Deepgram.Library.Initialize();
// OR very chatty logging
//Deepgram.Library.Initialize(LogLevel.Verbose); // LogLevel.Default, LogLevel.Debug, LogLevel.Verbose

// Initialize the microphone library
Deepgram.Microphone.Library.Initialize();

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Enhance library initialization with proper cleanup.

The library initialization should be wrapped in a try-finally block to ensure proper cleanup even if an exception occurs during initialization.

 try
 {
     // Initialize Library with default logging
     // Normal logging is "Info" level
     Deepgram.Library.Initialize();
-    // OR very chatty logging
-    //Deepgram.Library.Initialize(LogLevel.Verbose); // LogLevel.Default, LogLevel.Debug, LogLevel.Verbose
+    // Initialize the microphone library
+    try
+    {
+        Deepgram.Microphone.Library.Initialize();
+    }
+    catch
+    {
+        Deepgram.Library.Terminate();
+        throw;
+    }
-    // Initialize the microphone library
-    Deepgram.Microphone.Library.Initialize();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try
{
// Initialize Library with default logging
// Normal logging is "Info" level
Deepgram.Library.Initialize();
// OR very chatty logging
//Deepgram.Library.Initialize(LogLevel.Verbose); // LogLevel.Default, LogLevel.Debug, LogLevel.Verbose
// Initialize the microphone library
Deepgram.Microphone.Library.Initialize();
try
{
// Initialize Library with default logging
// Normal logging is "Info" level
Deepgram.Library.Initialize();
// Initialize the microphone library
try
{
Deepgram.Microphone.Library.Initialize();
}
catch
{
Deepgram.Library.Terminate();
throw;
}

Comment on lines +57 to +113
using (BinaryWriter writer = new BinaryWriter(File.Open($"output_{audioFileCount}.wav", FileMode.Append)))
{
Console.WriteLine("Adding WAV header to output.wav");
byte[] wavHeader = new byte[44];
int sampleRate = 48000;
short bitsPerSample = 16;
short channels = 1;
int byteRate = sampleRate * channels * (bitsPerSample / 8);
short blockAlign = (short)(channels * (bitsPerSample / 8));

wavHeader[0] = 0x52; // R
wavHeader[1] = 0x49; // I
wavHeader[2] = 0x46; // F
wavHeader[3] = 0x46; // F
wavHeader[4] = 0x00; // Placeholder for file size (will be updated later)
wavHeader[5] = 0x00; // Placeholder for file size (will be updated later)
wavHeader[6] = 0x00; // Placeholder for file size (will be updated later)
wavHeader[7] = 0x00; // Placeholder for file size (will be updated later)
wavHeader[8] = 0x57; // W
wavHeader[9] = 0x41; // A
wavHeader[10] = 0x56; // V
wavHeader[11] = 0x45; // E
wavHeader[12] = 0x66; // f
wavHeader[13] = 0x6D; // m
wavHeader[14] = 0x74; // t
wavHeader[15] = 0x20; // Space
wavHeader[16] = 0x10; // Subchunk1Size (16 for PCM)
wavHeader[17] = 0x00; // Subchunk1Size
wavHeader[18] = 0x00; // Subchunk1Size
wavHeader[19] = 0x00; // Subchunk1Size
wavHeader[20] = 0x01; // AudioFormat (1 for PCM)
wavHeader[21] = 0x00; // AudioFormat
wavHeader[22] = (byte)channels; // NumChannels
wavHeader[23] = 0x00; // NumChannels
wavHeader[24] = (byte)(sampleRate & 0xFF); // SampleRate
wavHeader[25] = (byte)((sampleRate >> 8) & 0xFF); // SampleRate
wavHeader[26] = (byte)((sampleRate >> 16) & 0xFF); // SampleRate
wavHeader[27] = (byte)((sampleRate >> 24) & 0xFF); // SampleRate
wavHeader[28] = (byte)(byteRate & 0xFF); // ByteRate
wavHeader[29] = (byte)((byteRate >> 8) & 0xFF); // ByteRate
wavHeader[30] = (byte)((byteRate >> 16) & 0xFF); // ByteRate
wavHeader[31] = (byte)((byteRate >> 24) & 0xFF); // ByteRate
wavHeader[32] = (byte)blockAlign; // BlockAlign
wavHeader[33] = 0x00; // BlockAlign
wavHeader[34] = (byte)bitsPerSample; // BitsPerSample
wavHeader[35] = 0x00; // BitsPerSample
wavHeader[36] = 0x64; // d
wavHeader[37] = 0x61; // a
wavHeader[38] = 0x74; // t
wavHeader[39] = 0x61; // a
wavHeader[40] = 0x00; // Placeholder for data chunk size (will be updated later)
wavHeader[41] = 0x00; // Placeholder for data chunk size (will be updated later)
wavHeader[42] = 0x00; // Placeholder for data chunk size (will be updated later)
wavHeader[43] = 0x00; // Placeholder for data chunk size (will be updated later)

writer.Write(wavHeader);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Extract WAV header creation into a separate method.

The WAV header creation logic is complex and uses magic numbers. Consider extracting it into a separate method with proper constants.

+    private static byte[] CreateWavHeader(int sampleRate, short bitsPerSample, short channels)
+    {
+        const int WAV_HEADER_SIZE = 44;
+        const int PCM_SUBCHUNK_SIZE = 16;
+        const short PCM_AUDIO_FORMAT = 1;
+
+        var wavHeader = new byte[WAV_HEADER_SIZE];
+        int byteRate = sampleRate * channels * (bitsPerSample / 8);
+        short blockAlign = (short)(channels * (bitsPerSample / 8));
+
+        // RIFF header
+        WriteString(wavHeader, 0, "RIFF");
+        // File size placeholder
+        WriteInt32(wavHeader, 4, 0);
+        // WAVE header
+        WriteString(wavHeader, 8, "WAVE");
+        // fmt chunk
+        WriteString(wavHeader, 12, "fmt ");
+        WriteInt32(wavHeader, 16, PCM_SUBCHUNK_SIZE);
+        WriteInt16(wavHeader, 20, PCM_AUDIO_FORMAT);
+        WriteInt16(wavHeader, 22, channels);
+        WriteInt32(wavHeader, 24, sampleRate);
+        WriteInt32(wavHeader, 28, byteRate);
+        WriteInt16(wavHeader, 32, blockAlign);
+        WriteInt16(wavHeader, 34, bitsPerSample);
+        // data chunk
+        WriteString(wavHeader, 36, "data");
+        WriteInt32(wavHeader, 40, 0);
+
+        return wavHeader;
+    }

Committable suggestion skipped: line range outside the PR's diff.

naomi-lgbt and others added 2 commits January 29, 2025 12:01
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
@jpvajda
Copy link
Contributor

jpvajda commented Jan 29, 2025

I tested the agent example and it runs with microphone successfully after I had to setup a symlink to port audio lib, so .NET could find it on my Mac:

But I think EndOfThought might be something we didn't end up introducing in the GA API as I got this error:

We might need to remove it.

2025-01-29 14:36:02.016 [Error] ProcessTextMessage: System.ArgumentException thrown Requested value 'EndOfThought' was not found.
----> {
  "type": "ConversationText",
  "role": "assistant",
  "content": "Hello!"
} received
----> Audio received
----> {
  "type": "AgentStartedSpeaking",
  "total_latency": 1.583769296,
  "tts_latency": 0.533172957,
  "ttt_latency": 0.465769599
} received
----> Audio received
----> Audio received
----> Audio received
----> Audio received
----> Audio received
----> Audio received
----> Audio received
----> Audio received
----> Audio received
----> Audio received
----> Audio received
----> {
  "type": "ConversationText",
  "role": "assistant",
  "content":  "How can I assist you today?"

@naomi-lgbt
Copy link
Contributor Author

Per discussion in Slack, it sounds like we are not supporting the EndOfThought event. Just to confirm, the SDK as-is does not support this:

image

@naomi-lgbt naomi-lgbt merged commit 393a5c8 into main Feb 3, 2025
3 checks passed
@naomi-lgbt naomi-lgbt deleted the feat/agent branch February 3, 2025 17:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants